package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.config.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributedLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class IndexService {

    @Autowired
    private GmallPmsClient pmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLock distributedLock;

    @Autowired
    private RedissonClient redissonClient;

    private static final String KEY_PREFIX = "index:category:";
    private static final String LOCK_PREFIX = "index:category:lock:";

    public List<CategoryEntity> queryLevelOneCategory() {
        ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryCategoryByPid(0L);
        return categoryResponseVo.getData();
    }

    @GmallCache(prefix = KEY_PREFIX, timeout = 129600, random = 14400, lock = LOCK_PREFIX)
    public List<CategoryEntity> queryLevelTwoAndThreeCategoryByPid(Long pid) {
        System.out.println("目标方法。。。。。。。");
        ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryLevelTwoAndThreeCategoriesByPid(pid);
        return categoryResponseVo.getData();
    }

    public List<CategoryEntity> queryLevelTwoAndThreeCategoryByPid2(Long pid) {
        // 1.先查询缓存，如果缓存命中则直接返回
        String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)) {
            return JSON.parseArray(json, CategoryEntity.class);
        }

        // 为了防止缓存击穿，添加分布式锁
        RLock fairLock = this.redissonClient.getFairLock(LOCK_PREFIX + pid);
        fairLock.lock();

        try {
            // 在获取分布式锁的过程中，可能有其他请求已经把数据放入缓存，此时再次确认缓存有没有，如果有则直接返回
            String json2 = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseArray(json2, CategoryEntity.class);
            }

            // 2.如果缓存没有命中则远程调用查询数据库放入缓存
            ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryLevelTwoAndThreeCategoriesByPid(pid);
            List<CategoryEntity> categoryEntities = categoryResponseVo.getData();
            if (CollectionUtils.isEmpty(categoryEntities)) {
                // 为了防止缓存穿透，数据即使为null也缓存，缓存时间不能太长（5min）
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 5, TimeUnit.MINUTES);
            } else {
                // 为了防止缓存雪崩，给缓存时间添加随机值。
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 90 + new Random().nextInt(5), TimeUnit.DAYS);
            }
            return categoryEntities;
        } finally {
            fairLock.unlock();
        }
    }

    public void testLock() {

        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();

        try {
            String json = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(json)) {
                this.redisTemplate.opsForValue().set("num", "1");
                return;
            }
            int num = Integer.parseInt(json);
            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
        } finally {
            lock.unlock();
        }
    }

    public void testLock3() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.distributedLock.tryLock("lock", uuid, 30);
        if (lock) {
            try {
                String json = this.redisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(json)) {
                    this.redisTemplate.opsForValue().set("num", "1");
                    return;
                }
                int num = Integer.parseInt(json);
                this.redisTemplate.opsForValue().set("num", String.valueOf(++num));

                try {
                    //this.testSubLock(uuid);
                    TimeUnit.SECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            } finally {
                this.distributedLock.unlock("lock", uuid);
            }
        }
    }

    public void testSubLock(String uuid) {
        this.distributedLock.tryLock("lock", uuid, 30);
        System.out.println("==============");
        this.distributedLock.unlock("lock", uuid);
    }

    /**
     * 大部分的互联网公司，就使用该方案即可，以后面试，可以选择该方案跟面试官说
     */
    public void testLock2() {
        // 加锁
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        if (!lock) {
            try {
                //  重试：递归，为了防止递归出现栈内存溢出、为了节省资源减少争抢，这里让它睡一会儿
                Thread.sleep(40);
                this.testLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } else {
            // this.redisTemplate.expire("lock", 3, TimeUnit.SECONDS);
            try {
                String json = this.redisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(json)) {
                    this.redisTemplate.opsForValue().set("num", "1");
                    return;
                }
                int num = Integer.parseInt(json);
                this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
            } finally {
                // 解锁：为了防止误删，释放锁之前需要先判断是否自己的锁，如果是则删除，否则不能删除
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                        "then " +
                        "   return redis.call('del', KEYS[1]) " +
                        "else " +
                        "   return 0 " +
                        "end";
                this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
//                if (StringUtils.equals(uuid, this.redisTemplate.opsForValue().get("lock"))){
//                    this.redisTemplate.delete("lock");
//                }
            }
        }
    }

    public void testRead() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        rwLock.readLock().lock(10, TimeUnit.SECONDS);
        System.out.println("==================");
        //rwLock.readLock().unlock();
    }

    public void testWrite() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        rwLock.writeLock().lock(10, TimeUnit.SECONDS);
        System.out.println("==================");
        //rwLock.writeLock().unlock();
    }

    public void testLatch() {
        try {
            RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
            cdl.trySetCount(6);
            cdl.await();

            // 锁门操作
            System.out.println("=======锁门=======");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public void testCountDown() {
        RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
        // 出来了一位同学
        System.out.println("==============");
        cdl.countDown();
    }
}